﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable disable

using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Runtime.InteropServices;
using static Interop;
using static Interop.User32;
using Hashtable = System.Collections.Hashtable;

namespace System.Windows.Forms
{
    /// <summary>
    ///
    ///  Displays a list with a checkbox to the left
    ///
    ///  of each item.
    /// </summary>
    [LookupBindingProperties]
    [SRDescription(nameof(SR.DescriptionCheckedListBox))]
    public partial class CheckedListBox : ListBox
    {
        private int _idealCheckSize = 13;

        private const int LB_CHECKED = 1;
        private const int LB_UNCHECKED = 0;
        private const int BORDER_SIZE = 1;

        /// <summary>
        ///  Decides whether or not to ignore the next LBN_SELCHANGE message - used to prevent cursor keys from
        ///  toggling checkboxes.
        /// </summary>
        private bool _killnextselect;

        /// <summary>
        ///  Current listener of the onItemCheck event.
        /// </summary>
        private ItemCheckEventHandler _onItemCheck;

        /// <summary>
        ///  Should we use 3d checkboxes or flat ones?
        /// </summary>
        private bool _flat = true;

        /// <summary>
        ///  Indicates which item was last selected.  We want to keep track of this so we can be a little less
        ///  aggressive about checking/unchecking the items as the user moves around.
        /// </summary>
        private int _lastSelected = -1;

        /// <summary>
        ///  The collection of checked items in the CheckedListBox.
        /// </summary>
        private CheckedItemCollection _checkedItemCollection;
        private CheckedIndexCollection _checkedIndexCollection;

        private static readonly WM LBC_GETCHECKSTATE = RegisterWindowMessageW("LBC_GETCHECKSTATE");
        private static readonly WM LBC_SETCHECKSTATE = RegisterWindowMessageW("LBC_SETCHECKSTATE");

        /// <summary>
        ///  Creates a new CheckedListBox for the user.
        /// </summary>
        public CheckedListBox() : base()
        {
            // If we eat WM_ERASEBKGRND messages, the background will be
            // painted sometimes but not others.
            // SetStyle(ControlStyles.Opaque, true);

            // If a long item is drawn with ellipsis, we must redraw the ellipsed part
            // as well as the newly uncovered region.
            SetStyle(ControlStyles.ResizeRedraw, true);
        }

        /// <summary>
        ///  Indicates whether or not the checkbox should be toggled whenever an
        ///  item is selected.  The default behaviour is to just change the
        ///  selection, and then make the user click again to check it.  However,
        ///  some may prefer checking the item as soon as it is clicked.
        /// </summary>
        [SRCategory(nameof(SR.CatBehavior))]
        [DefaultValue(false)]
        [SRDescription(nameof(SR.CheckedListBoxCheckOnClickDescr))]
        public bool CheckOnClick { get; set; }

        /// <summary>
        ///  Collection of checked indices in this CheckedListBox.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public CheckedIndexCollection CheckedIndices
        {
            get
            {
                if (_checkedIndexCollection is null)
                {
                    _checkedIndexCollection = new CheckedIndexCollection(this);
                }

                return _checkedIndexCollection;
            }
        }

        /// <summary>
        ///  Collection of checked items in this CheckedListBox.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public CheckedItemCollection CheckedItems
        {
            get
            {
                if (_checkedItemCollection is null)
                {
                    _checkedItemCollection = new CheckedItemCollection(this);
                }

                return _checkedItemCollection;
            }
        }

        /// <summary>
        ///  This is called when creating a window.  Inheriting classes can override
        ///  this to add extra functionality, but should not forget to first call
        ///  base.CreateParams() to make sure the control continues to work
        ///  correctly.
        /// </summary>
        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;
                cp.Style |= (int)(LBS.OWNERDRAWFIXED | LBS.WANTKEYBOARDINPUT);
                return cp;
            }
        }

        /// <summary>
        ///  CheckedListBox DataSource.
        /// </summary>
        /// <hideinheritance/>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public new object DataSource
        {
            get => base.DataSource;
            set => base.DataSource = value;
        }

        /// <summary>
        ///  CheckedListBox DisplayMember.
        /// </summary>
        /// <hideinheritance/>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public new string DisplayMember
        {
            get => base.DisplayMember;
            set => base.DisplayMember = value;
        }

        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public override DrawMode DrawMode
        {
            get
            {
                return DrawMode.Normal;
            }
            set
            {
            }
        }

        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public override int ItemHeight
        {
            get
            {
                // this should take FontHeight + buffer into Consideration.
                return Font.Height + scaledListItemBordersHeight;
            }
            set
            {
            }
        }

        /// <summary>
        ///  Collection of items in this listbox.
        /// </summary>
        [SRCategory(nameof(SR.CatData))]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [Localizable(true)]
        [SRDescription(nameof(SR.ListBoxItemsDescr))]
        [Editor("System.Windows.Forms.Design.ListControlStringCollectionEditor, " + AssemblyRef.SystemDesign, typeof(UITypeEditor))]
        new public ObjectCollection Items
        {
            get
            {
                return (ObjectCollection)base.Items;
            }
        }

        // Computes the maximum width of all items in the ListBox
        //
        internal override int MaxItemWidth
        {
            get
            {
                // Overridden to include the size of the checkbox
                // Allows for one pixel either side of the checkbox, plus another 1 pixel buffer = 3 pixels
                //
                return base.MaxItemWidth + _idealCheckSize + scaledListItemPaddingBuffer;
            }
        }

        /// <summary>
        ///  For CheckedListBoxes, multi-selection is not supported.  You can set
        ///  selection to be able to select one item or no items.
        /// </summary>
        public override SelectionMode SelectionMode
        {
            get => base.SelectionMode;
            set
            {
                //valid values are 0x0 to 0x3
                SourceGenerated.EnumValidator.Validate(value);
                if (value != SelectionMode.One
                    && value != SelectionMode.None)
                {
                    throw new ArgumentException(SR.CheckedListBoxInvalidSelectionMode);
                }

                if (value != SelectionMode)
                {
                    base.SelectionMode = value;
                    RecreateHandle();
                }
            }
        }

        /// <summary>
        ///  Indicates if the CheckBoxes should show up as flat or 3D in appearance.
        /// </summary>
        [SRCategory(nameof(SR.CatAppearance))]
        [DefaultValue(false)]
        [SRDescription(nameof(SR.CheckedListBoxThreeDCheckBoxesDescr))]
        public bool ThreeDCheckBoxes
        {
            get
            {
                return !_flat;
            }
            set
            {
                // change the style and repaint.
                //
                if (_flat == value)
                {
                    _flat = !value;

                    // see if we have some items, and only invalidate if we do.
                    ObjectCollection items = (ObjectCollection)Items;
                    if ((items is not null) && (items.Count > 0))
                    {
                        Invalidate();
                    }
                }
            }
        }

        /// <summary>
        ///  Determines whether to use compatible text rendering engine (GDI+) or not (GDI).
        /// </summary>
        [DefaultValue(false)]
        [SRCategory(nameof(SR.CatBehavior))]
        [SRDescription(nameof(SR.UseCompatibleTextRenderingDescr))]
        public bool UseCompatibleTextRendering
        {
            get => base.UseCompatibleTextRenderingInt;
            set => base.UseCompatibleTextRenderingInt = value;
        }

        /// <summary>
        ///  Determines whether the control supports rendering text using GDI+ and GDI.
        ///  This is provided for container controls to iterate through its children to set UseCompatibleTextRendering to the same
        ///  value if the child control supports it.
        /// </summary>
        internal override bool SupportsUseCompatibleTextRendering
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        ///  CheckedListBox ValueMember.
        /// </summary>
        /// <hideinheritance/>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public new string ValueMember
        {
            get => base.ValueMember;
            set => base.ValueMember = value;
        }

        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        new public event EventHandler DataSourceChanged
        {
            add => base.DataSourceChanged += value;
            remove => base.DataSourceChanged -= value;
        }

        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        new public event EventHandler DisplayMemberChanged
        {
            add => base.DisplayMemberChanged += value;
            remove => base.DisplayMemberChanged -= value;
        }

        [SRCategory(nameof(SR.CatBehavior))]
        [SRDescription(nameof(SR.CheckedListBoxItemCheckDescr))]
        public event ItemCheckEventHandler ItemCheck
        {
            add => _onItemCheck += value;
            remove => _onItemCheck -= value;
        }

        /// <hideinheritance/>
        [Browsable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        public new event EventHandler Click
        {
            add => base.Click += value;
            remove => base.Click -= value;
        }

        /// <hideinheritance/>
        [Browsable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        public new event MouseEventHandler MouseClick
        {
            add => base.MouseClick += value;
            remove => base.MouseClick -= value;
        }

        /// <hideinheritance/>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public new event DrawItemEventHandler DrawItem
        {
            add => base.DrawItem += value;
            remove => base.DrawItem -= value;
        }

        /// <hideinheritance/>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public new event MeasureItemEventHandler MeasureItem
        {
            add => base.MeasureItem += value;
            remove => base.MeasureItem -= value;
        }

        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new Padding Padding
        {
            get => base.Padding;
            set => base.Padding = value;
        }

        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        new public event EventHandler ValueMemberChanged
        {
            add => base.ValueMemberChanged += value;
            remove => base.ValueMemberChanged -= value;
        }

        /// <summary>
        ///  Constructs the new instance of the accessibility object for this control. Subclasses
        ///  should not call base.CreateAccessibilityObject.
        /// </summary>
        protected override AccessibleObject CreateAccessibilityInstance()
        {
            return new CheckedListBoxAccessibleObject(this);
        }

        protected override ListBox.ObjectCollection CreateItemCollection()
        {
            return new ObjectCollection(this);
        }

        /// <summary>
        ///  Gets the check value of the current item.  This value will be from the
        ///  System.Windows.Forms.CheckState enumeration.
        /// </summary>
        public CheckState GetItemCheckState(int index)
        {
            if (index < 0 || index >= Items.Count)
            {
                throw new ArgumentOutOfRangeException(nameof(index), index, string.Format(SR.InvalidArgument, nameof(index), index));
            }

            return CheckedItems.GetCheckedState(index);
        }

        /// <summary>
        ///  Indicates if the given item is, in any way, shape, or form, checked.
        ///  This will return true if the item is fully or indeterminately checked.
        /// </summary>
        public bool GetItemChecked(int index)
        {
            return (GetItemCheckState(index) != CheckState.Unchecked);
        }

        /// <summary>
        ///  Invalidates the given item in the listbox
        /// </summary>
        private unsafe void InvalidateItem(int index)
        {
            if (IsHandleCreated)
            {
                var rect = new RECT();
                SendMessageW(this, (WM)LB.GETITEMRECT, (IntPtr)index, ref rect);
                InvalidateRect(new HandleRef(this, Handle), &rect, BOOL.FALSE);
            }
        }

        /// <summary>
        ///  A redirected LBN_SELCHANGE message notification.
        /// </summary>
        private void LbnSelChange()
        {
            // prepare to change the selection.  we'll fire an event for
            // this.  Note that we'll only change the selection when the
            // user clicks again on a currently selected item, or when the
            // user has CheckOnClick set to true.  Otherwise
            // just using the up and down arrows selects or deselects
            // every item around town ...
            //

            // Get the index of the item to check/uncheck
            int index = SelectedIndex;

            // make sure we have a valid index, otherwise we're going to
            // fail ahead...
            if (index < 0 || index >= Items.Count)
            {
                return;
            }

            // Send an accessibility notification
            //
            AccessibilityNotifyClients(AccessibleEvents.Focus, index);
            AccessibilityNotifyClients(AccessibleEvents.Selection, index);

            //# VS7 86
            if (!_killnextselect && (index == _lastSelected || CheckOnClick == true))
            {
                CheckState currentValue = CheckedItems.GetCheckedState(index);
                CheckState newValue = (currentValue != CheckState.Unchecked)
                                      ? CheckState.Unchecked
                                      : CheckState.Checked;

                ItemCheckEventArgs itemCheckEvent = new ItemCheckEventArgs(index, newValue, currentValue);
                OnItemCheck(itemCheckEvent);

                // take whatever value the user set, and set that as the value.
                //
                CheckedItems.SetCheckedState(index, itemCheckEvent.NewValue);

                // Send accessibility notifications for state change
                AccessibilityNotifyClients(AccessibleEvents.StateChange, index);
                AccessibilityNotifyClients(AccessibleEvents.NameChange, index);
            }

            _lastSelected = index;
            InvalidateItem(index);
        }

        /// <summary>
        ///  Ensures that mouse clicks can toggle...
        /// </summary>
        protected override void OnClick(EventArgs e)
        {
            _killnextselect = false;
            base.OnClick(e);
        }

        /// <summary>
        ///  When the handle is created we can dump any cached item-check pairs.
        /// </summary>
        protected override void OnHandleCreated(EventArgs e)
        {
            base.OnHandleCreated(e);
            SendMessageW(this, (WM)LB.SETITEMHEIGHT, IntPtr.Zero, (IntPtr)ItemHeight);
        }

        /// <summary>
        ///  Actually goes and fires the drawItem event.  Inheriting controls
        ///  should use this to know when the event is fired [this is preferable to
        ///  adding an event handler yourself for this event].  They should,
        ///  however, remember to call base.OnDrawItem(e); to ensure the event is
        ///  still fired to external listeners
        /// </summary>
        protected override void OnDrawItem(DrawItemEventArgs e)
        {
            object item;

            if (Font.Height < 0)
            {
                Font = Control.DefaultFont;
            }

            if (e.Index >= 0)
            {
                if (e.Index < Items.Count)
                {
                    item = Items[e.Index];
                }
                else
                {
                    // If the item is not part of our collection, we will just get the string for it and display it.

                    item = NativeGetItemText(e.Index);
                }

                Rectangle bounds = e.Bounds;
                int height = ItemHeight;

                // Set up the appearance of the checkbox

                ButtonState state = ButtonState.Normal;
                if (_flat)
                {
                    state |= ButtonState.Flat;
                }

                if (e.Index < Items.Count)
                {
                    switch (CheckedItems.GetCheckedState(e.Index))
                    {
                        case CheckState.Checked:
                            state |= ButtonState.Checked;
                            break;
                        case CheckState.Indeterminate:
                            state |= ButtonState.Checked | ButtonState.Inactive;
                            break;
                    }
                }

                // If we are drawing themed CheckBox get the size from the renderer which can change with DPI.
                if (Application.RenderWithVisualStyles)
                {
                    VisualStyles.CheckBoxState cbState = CheckBoxRenderer.ConvertFromButtonState(
                        state,
                        isMixed: false,
                        (e.State & DrawItemState.HotLight) == DrawItemState.HotLight);

                    _idealCheckSize = CheckBoxRenderer.GetGlyphSize(e, cbState, HandleInternal).Width;
                }

                // Determine bounds for the checkbox
                int centeringFactor = Math.Max((height - _idealCheckSize) / 2, 0);

                // Keep the checkbox within the item's upper and lower bounds
                if (centeringFactor + _idealCheckSize > bounds.Height)
                {
                    centeringFactor = bounds.Height - _idealCheckSize;
                }

                Rectangle box = new Rectangle(
                    bounds.X + scaledListItemStartPosition,
                    bounds.Y + centeringFactor,
                    _idealCheckSize,
                    _idealCheckSize);

                if (RightToLeft == RightToLeft.Yes)
                {
                    // For a RightToLeft checked list box, we want the checkbox
                    // to be drawn at the right.
                    // So we override the X position.
                    box.X = bounds.X + bounds.Width - _idealCheckSize - scaledListItemStartPosition;
                }

                // Draw the checkbox.

                if (Application.RenderWithVisualStyles)
                {
                    VisualStyles.CheckBoxState cbState = CheckBoxRenderer.ConvertFromButtonState(
                        state,
                        isMixed: false,
                        ((e.State & DrawItemState.HotLight) == DrawItemState.HotLight));

                    CheckBoxRenderer.DrawCheckBoxWithVisualStyles(e, new Point(box.X, box.Y), cbState, HandleInternal);
                }
                else
                {
                    ControlPaint.DrawCheckBox(e.Graphics, box, state);
                }

                // Determine bounds for the text portion of the item
                Rectangle textBounds = new Rectangle(
                    bounds.X + _idealCheckSize + (scaledListItemStartPosition * 2),
                    bounds.Y,
                    bounds.Width - (_idealCheckSize + (scaledListItemStartPosition * 2)),
                    bounds.Height);

                if (RightToLeft == RightToLeft.Yes)
                {
                    // For a RightToLeft checked list box, we want the text
                    // to be drawn at the left.
                    // So we override the X position.
                    textBounds.X = bounds.X;
                }

                Color backColor = (SelectionMode != SelectionMode.None) ? e.BackColor : BackColor;
                Color foreColor = (SelectionMode != SelectionMode.None) ? e.ForeColor : ForeColor;
                if (!Enabled)
                {
                    foreColor = SystemColors.GrayText;
                }

                Font font = Font;

                // Setup text font, color, and text

                string text = GetItemText(item);

                if (SelectionMode != SelectionMode.None && (e.State & DrawItemState.Selected) == DrawItemState.Selected)
                {
                    if (Enabled)
                    {
                        backColor = SystemColors.Highlight;
                        foreColor = SystemColors.HighlightText;
                    }
                    else
                    {
                        backColor = SystemColors.InactiveBorder;
                        foreColor = SystemColors.GrayText;
                    }
                }

                // Draw the text

                // Due to some sort of unpredictable painting optimization in the Windows ListBox control,
                // we need to always paint the background rectangle for the current line.

                if (!backColor.HasTransparency())
                {
                    using var hdc = new DeviceContextHdcScope(e);
                    using var hbrush = new Gdi32.CreateBrushScope(backColor);
                    hdc.FillRectangle(textBounds, hbrush);
                }
                else
                {
                    // Need to use GDI+
                    using var brush = backColor.GetCachedSolidBrushScope();
                    e.GraphicsInternal.FillRectangle(brush, textBounds);
                }

                Rectangle stringBounds = new Rectangle(
                    textBounds.X + BORDER_SIZE,
                    textBounds.Y,
                    textBounds.Width - BORDER_SIZE,
                    textBounds.Height - 2 * BORDER_SIZE); // minus borders

                if (UseCompatibleTextRendering)
                {
                    using StringFormat format = new StringFormat();
                    if (UseTabStops)
                    {
                        //  Set tab stops so it looks similar to a ListBox, at least with the default font size.
                        float tabDistance = 3.6f * Font.Height; // about 7 characters
                        float[] tabStops = new float[15];
                        float tabOffset = -(_idealCheckSize + (scaledListItemStartPosition * 2));
                        for (int i = 1; i < tabStops.Length; i++)
                        {
                            tabStops[i] = tabDistance;
                        }

                        //(
                        if (Math.Abs(tabOffset) < tabDistance)
                        {
                            tabStops[0] = tabDistance + tabOffset;
                        }
                        else
                        {
                            tabStops[0] = tabDistance;
                        }

                        format.SetTabStops(0, tabStops);
                    }
                    else if (UseCustomTabOffsets)
                    {
                        //Set TabStops to userDefined values
                        int wpar = CustomTabOffsets.Count;
                        float[] tabStops = new float[wpar];
                        CustomTabOffsets.CopyTo(tabStops, 0);
                        format.SetTabStops(0, tabStops);
                    }

                    // Adjust string format for Rtl controls
                    if (RightToLeft == RightToLeft.Yes)
                    {
                        format.FormatFlags |= StringFormatFlags.DirectionRightToLeft;
                    }

                    // ListBox doesn't word-wrap its items, so neither should CheckedListBox
                    //
                    format.FormatFlags |= StringFormatFlags.NoWrap;

                    // Set Trimming to None to prevent DrawString() from whacking the entire
                    // string when there is only one character per tab included in the string.
                    format.Trimming = StringTrimming.None;

                    // Do actual drawing
                    using var brush = foreColor.GetCachedSolidBrushScope();
                    e.Graphics.DrawString(text, font, brush, stringBounds, format);
                }
                else
                {
                    TextFormatFlags flags = TextFormatFlags.Default;
                    flags |= TextFormatFlags.NoPrefix;

                    if (UseTabStops || UseCustomTabOffsets)
                    {
                        flags |= TextFormatFlags.ExpandTabs;
                    }

                    // Adjust string format for Rtl controls
                    if (RightToLeft == RightToLeft.Yes)
                    {
                        flags |= TextFormatFlags.RightToLeft;
                        flags |= TextFormatFlags.Right;
                    }

                    // Do actual drawing
                    TextRenderer.DrawText(e, text, font, stringBounds, foreColor, flags);
                }

                // Draw the focus rect if required

                if ((e.State & DrawItemState.Focus) == DrawItemState.Focus &&
                    (e.State & DrawItemState.NoFocusRect) != DrawItemState.NoFocusRect)
                {
                    ControlPaint.DrawFocusRectangle(e.Graphics, textBounds, foreColor, backColor);
                }
            }

            if (Items.Count == 0 &&
                e.Bounds.Width > 2 * BORDER_SIZE && e.Bounds.Height > 2 * BORDER_SIZE)
            {
                Color backColor = (SelectionMode != SelectionMode.None) ? e.BackColor : BackColor;
                Rectangle bounds = e.Bounds;
                Rectangle emptyRectangle = new Rectangle(
                    bounds.X + BORDER_SIZE,
                    bounds.Y,
                    bounds.Width - BORDER_SIZE,
                    bounds.Height - 2 * BORDER_SIZE); // Upper and lower borders.

                if (Focused)
                {
                    // Draw focus rectangle for virtual first item in the list if there are no items in the list.
                    Color foreColor = (SelectionMode != SelectionMode.None) ? e.ForeColor : ForeColor;
                    if (!Enabled)
                    {
                        foreColor = SystemColors.GrayText;
                    }

                    ControlPaint.DrawFocusRectangle(e.Graphics, emptyRectangle, foreColor, backColor);
                }
                else if (!Application.RenderWithVisualStyles)
                {
                    // If VisualStyles are off, rectangle needs to be explicitly erased, when focus is lost.
                    // This is because of persisting empty focus rectangle when VisualStyles are off.
                    using var brush = backColor.GetCachedSolidBrushScope();
                    e.Graphics.FillRectangle(brush, emptyRectangle);
                }
            }
        }

        protected unsafe override void OnBackColorChanged(EventArgs e)
        {
            base.OnBackColorChanged(e);

            if (IsHandleCreated)
            {
                InvalidateRect(new HandleRef(this, Handle), null, BOOL.TRUE);
            }
        }

        protected override void OnFontChanged(EventArgs e)
        {
            // Update the item height
            //
            if (IsHandleCreated)
            {
                SendMessageW(this, (WM)LB.SETITEMHEIGHT, IntPtr.Zero, (IntPtr)ItemHeight);
            }

            // The base OnFontChanged will adjust the height of the CheckedListBox accordingly
            //
            base.OnFontChanged(e);
        }

        /// <summary>
        ///  This is the code that actually fires the "keyPress" event.  The Checked
        ///  ListBox overrides this to look for space characters, since we
        ///  want to use those to check or uncheck items periodically.  Don't
        ///  forget to call base.OnKeyPress() to ensure that KeyPrese events
        ///  are correctly fired for all other keys.
        /// </summary>
        protected override void OnKeyPress(KeyPressEventArgs e)
        {
            if (e.KeyChar == ' ' && SelectionMode != SelectionMode.None)
            {
                LbnSelChange();
            }

            if (FormattingEnabled) //We want to fire KeyPress only when FormattingEnabled (this is a whidbey property)
            {
                base.OnKeyPress(e);
            }
        }

        /// <summary>
        ///  This is the code that actually fires the itemCheck event.  Don't
        ///  forget to call base.onItemCheck() to ensure that itemCheck vents
        ///  are correctly fired for all other keys.
        /// </summary>
        protected virtual void OnItemCheck(ItemCheckEventArgs ice)
        {
            _onItemCheck?.Invoke(this, ice);
        }

        protected override void OnMeasureItem(MeasureItemEventArgs e)
        {
            base.OnMeasureItem(e);

            // we'll use the ideal checkbox size plus enough for padding on the top
            // and bottom
            //
            if (e.ItemHeight < _idealCheckSize + 2)
            {
                e.ItemHeight = _idealCheckSize + 2;
            }
        }

        /// <summary>
        ///  Actually goes and fires the selectedIndexChanged event.  Inheriting controls
        ///  should use this to know when the event is fired [this is preferable to
        ///  adding an event handler on yourself for this event].  They should,
        ///  however, remember to call base.OnSelectedIndexChanged(e); to ensure the event is
        ///  still fired to external listeners
        /// </summary>
        protected override void OnSelectedIndexChanged(EventArgs e)
        {
            base.OnSelectedIndexChanged(e);
            _lastSelected = SelectedIndex;
        }

        /// <summary>
        ///  Reparses the objects, getting new text strings for them.
        /// </summary>
        protected override void RefreshItems()
        {
            Hashtable savedcheckedItems = new Hashtable();
            for (int i = 0; i < Items.Count; i++)
            {
                savedcheckedItems[i] = CheckedItems.GetCheckedState(i);
            }

            //call the base
            base.RefreshItems();
            // restore the checkedItems...

            for (int j = 0; j < Items.Count; j++)
            {
                CheckedItems.SetCheckedState(j, (CheckState)savedcheckedItems[j]);
            }
        }

        /// <summary>
        ///  Sets the checked value of the given item.  This value should be from
        ///  the System.Windows.Forms.CheckState enumeration.
        /// </summary>
        public void SetItemCheckState(int index, CheckState value)
        {
            if (index < 0 || index >= Items.Count)
            {
                throw new ArgumentOutOfRangeException(nameof(index), index, string.Format(SR.InvalidArgument, nameof(index), index));
            }

            // valid values are 0-2 inclusive.
            SourceGenerated.EnumValidator.Validate(value);
            CheckState currentValue = CheckedItems.GetCheckedState(index);

            if (value != currentValue)
            {
                ItemCheckEventArgs itemCheckEvent = new ItemCheckEventArgs(index, value, currentValue);
                OnItemCheck(itemCheckEvent);

                if (itemCheckEvent.NewValue != currentValue)
                {
                    CheckedItems.SetCheckedState(index, itemCheckEvent.NewValue);
                    InvalidateItem(index);
                }
            }
        }

        /// <summary>
        ///  Sets the checked value of the given item.  This value should be a
        ///  boolean.
        /// </summary>
        public void SetItemChecked(int index, bool value)
        {
            SetItemCheckState(index, value ? CheckState.Checked : CheckState.Unchecked);
        }

        /// <summary>
        ///  We need to get LBN_SELCHANGE notifications
        /// </summary>
        protected override void WmReflectCommand(ref Message m)
        {
            switch (PARAM.HIWORD(m.WParam))
            {
                case (int)LBN.SELCHANGE:
                    LbnSelChange();
                    // finally, fire the OnSelectionChange event.
                    base.WmReflectCommand(ref m);
                    break;

                case (int)LBN.DBLCLK:
                    // We want double-clicks to change the checkstate on each click - just like the CheckBox control
                    //
                    LbnSelChange();
                    base.WmReflectCommand(ref m);
                    break;

                default:
                    base.WmReflectCommand(ref m);
                    break;
            }
        }

        /// <summary>
        ///  Handle keyboard input to prevent arrow keys from toggling
        ///  checkboxes in CheckOnClick mode.
        /// </summary>
        private void WmReflectVKeyToItem(ref Message m)
        {
            int keycode = PARAM.LOWORD(m.WParam);
            switch ((Keys)keycode)
            {
                case Keys.Up:
                case Keys.Down:
                case Keys.PageUp:
                case Keys.PageDown:
                case Keys.Home:
                case Keys.End:
                case Keys.Left:
                case Keys.Right:
                    _killnextselect = true;
                    break;
                default:
                    _killnextselect = false;
                    break;
            }

            m.Result = NativeMethods.InvalidIntPtr;
        }

        /// <summary>
        ///  The listbox's window procedure.  Inheriting classes can override this
        ///  to add extra functionality, but should not forget to call
        ///  base.wndProc(m); to ensure the button continues to function properly.
        /// </summary>
        protected override void WndProc(ref Message m)
        {
            switch ((WM)m.Msg)
            {
                case WM.REFLECT_CHARTOITEM:
                    m.Result = NativeMethods.InvalidIntPtr;
                    break;
                case WM.REFLECT_VKEYTOITEM:
                    WmReflectVKeyToItem(ref m);
                    break;
                default:
                    if (m.Msg == (int)LBC_GETCHECKSTATE)
                    {
                        int item = unchecked((int)(long)m.WParam);
                        if (item < 0 || item >= Items.Count)
                        {
                            m.Result = (IntPtr)LB_ERR;
                        }
                        else
                        {
                            m.Result = (IntPtr)(GetItemChecked(item) ? LB_CHECKED : LB_UNCHECKED);
                        }
                    }
                    else if (m.Msg == (int)LBC_SETCHECKSTATE)
                    {
                        int item = unchecked((int)(long)m.WParam);
                        int state = unchecked((int)(long)m.LParam);
                        if (item < 0 || item >= Items.Count || (state != LB_CHECKED && state != LB_UNCHECKED))
                        {
                            m.Result = IntPtr.Zero;
                        }
                        else
                        {
                            SetItemChecked(item, (state == LB_CHECKED));
                            m.Result = (IntPtr)1;
                        }
                    }
                    else
                    {
                        base.WndProc(ref m);
                    }

                    break;
            }
        }

        internal override bool SupportsUiaProviders => false;
    }
}
